/*
 * presets.c -- Apply presets
 *
 *	Copyright (c) 2002-2008 Gabriel Bouvigne
 *	Copyright (c) 2007-2012 Robert Hegemann
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */


#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "lame.h"
#include "machine.h"
#include "set_get.h"
#include "encoder.h"
#include "util.h"
#include "lame_global_flags.h"

#define SET_OPTION(opt, val, def) if (enforce) \
    (void) lame_set_##opt(gfp, val); \
    else if (!(fabs(lame_get_##opt(gfp) - def) > 0)) \
    (void) lame_set_##opt(gfp, val);

#define SET__OPTION(opt, val, def) if (enforce) \
    lame_set_##opt(gfp, val); \
    else if (!(fabs(lame_get_##opt(gfp) - def) > 0)) \
    lame_set_##opt(gfp, val);

#undef Min
#undef Max

static inline int
min_int(int a, int b) {
    if (a < b) {
        return a;
    }
    return b;
}

static inline int
max_int(int a, int b) {
    if (a > b) {
        return a;
    }
    return b;
}


typedef struct {
    int vbr_q;
    int quant_comp;
    int quant_comp_s;
    int expY;
    FLOAT st_lrm;          /*short threshold */
    FLOAT st_s;
    FLOAT masking_adj;
    FLOAT masking_adj_short;
    FLOAT ath_lower;
    FLOAT ath_curve;
    FLOAT ath_sensitivity;
    FLOAT interch;
    int safejoint;
    int sfb21mod;
    FLOAT msfix;
    FLOAT minval;
    FLOAT ath_fixpoint;
} vbr_presets_t;

/* *INDENT-OFF* */

/* Switch mappings for VBR mode VBR_RH */
static const vbr_presets_t vbr_old_switch_map[] = {
        /*vbr_q  qcomp_l  qcomp_s  expY  st_lrm   st_s  mask adj_l  adj_s  ath_lower  ath_curve  ath_sens  interChR  safejoint sfb21mod  msfix */
        {0,  9, 9, 0, 5.20, 125.0, -4.2, -6.3, 4.8,   1,   0,   0,      2, 21, 0.97, 5, 100},
        {1,  9, 9, 0, 5.30, 125.0, -3.6, -5.6, 4.5,   1.5, 0,   0,      2, 21, 1.35, 5, 100},
        {2,  9, 9, 0, 5.60, 125.0, -2.2, -3.5, 2.8,   2,   0,   0,      2, 21, 1.49, 5, 100},
        {3,  9, 9, 1, 5.80, 130.0, -1.8, -2.8, 2.6,   3,   -4,  0,      2, 20, 1.64, 5, 100},
        {4,  9, 9, 1, 6.00, 135.0, -0.7, -1.1, 1.1,   3.5, -8,  0,      2, 0,  1.79, 5, 100},
        {5,  9, 9, 1, 6.40, 140.0, 0.5,  0.4,  -7.5,  4,   -12, 0.0002, 0, 0,  1.95, 5, 100},
        {6,  9, 9, 1, 6.60, 145.0, 0.67, 0.65, -14.7, 6.5, -19, 0.0004, 0, 0,  2.30, 5, 100},
        {7,  9, 9, 1, 6.60, 145.0, 0.8,  0.75, -19.7, 8,   -22, 0.0006, 0, 0,  2.70, 5, 100},
        {8,  9, 9, 1, 6.60, 145.0, 1.2,  1.15, -27.5, 10,  -23, 0.0007, 0, 0,  0,    5, 100},
        {9,  9, 9, 1, 6.60, 145.0, 1.6,  1.6,  -36,   11,  -25, 0.0008, 0, 0,  0,    5, 100},
        {10, 9, 9, 1, 6.60, 145.0, 2.0,  2.0,  -36,   12,  -25, 0.0008, 0, 0,  0,    5, 100}
};

static const vbr_presets_t vbr_mt_psy_switch_map[] = {
        /*vbr_q  qcomp_l  qcomp_s  expY  st_lrm   st_s  mask adj_l  adj_s  ath_lower  ath_curve  ath_sens  ---  safejoint sfb21mod  msfix */
        {0, 9, 9, 0, 4.20, 25.0, -6.8, -6.8, 7.1, 1, 0, 0, 2, 31, 1.000, 5, 100},
        {1, 9, 9, 0, 4.20, 25.0, -4.8, -4.8, 5.4, 1.4, -1, 0, 2, 27, 1.122, 5, 98},
        {2, 9, 9, 0, 4.20, 25.0, -2.6, -2.6, 3.7, 2.0, -3, 0, 2, 23, 1.288, 5, 97},
        {3, 9, 9, 1, 4.20, 25.0, -1.6, -1.6, 2.0, 2.0, -5, 0, 2, 18, 1.479, 5, 96},
        {4, 9, 9, 1, 4.20, 25.0, -0.0, -0.0, 0.0, 2.0, -8, 0, 2, 12, 1.698, 5, 95},
        {5, 9, 9, 1, 4.20, 25.0, 1.3, 1.3, -6, 3.5, -11, 0, 2, 8, 1.950, 5, 94.2},
#if 0
        {6,       9,       9,    1,   4.50, 100.0,       1.5,    1.5,     -24.0,       6.0,      -14,   0,         2,       4,  2.239,  3,  93.9},
        {7,       9,       9,    1,   4.80, 200.0,       1.7,    1.7,     -28.0,       9.0,      -20,   0,         2,       0,  2.570,  1,  93.6},
#else
        {6, 9, 9, 1, 4.50, 100.0, 2.2, 2.3, -12.0, 6.0, -14, 0, 2, 4, 2.239, 3, 93.9},
        {7, 9, 9, 1, 4.80, 200.0, 2.7, 2.7, -18.0, 9.0, -17, 0, 2, 0, 2.570, 1, 93.6},
#endif
        {8, 9, 9, 1, 5.30, 300.0, 2.8, 2.8, -21.0, 10.0, -23, 0.0002, 0, 0, 2.951, 0, 93.3},
        {9, 9, 9, 1, 6.60, 300.0, 2.8, 2.8, -23.0, 11.0, -25, 0.0006, 0, 0, 3.388, 0, 93.3},
        {10, 9, 9, 1, 25.00, 300.0, 2.8, 2.8, -25.0, 12.0, -27, 0.0025, 0, 0, 3.500, 0, 93.3}
};

/* *INDENT-ON* */

static vbr_presets_t const *
get_vbr_preset(int v) {
    switch (v) {
        case vbr_mtrh:
        case vbr_mt:
            return &vbr_mt_psy_switch_map[0];
        default:
            return &vbr_old_switch_map[0];
    }
}

#define NOOP(m) (void)p.m
#define LERP(m) (p.m = p.m + x * (q.m - p.m))

static void
apply_vbr_preset(lame_global_flags *gfp, int a, int enforce) {
    vbr_presets_t const *vbr_preset = get_vbr_preset(lame_get_VBR(gfp));
    float x = gfp->VBR_q_frac;
    vbr_presets_t p = vbr_preset[a];
    vbr_presets_t q = vbr_preset[a + 1];
    vbr_presets_t const *set = &p;

    NOOP(vbr_q);
    NOOP(quant_comp);
    NOOP(quant_comp_s);
    NOOP(expY);
    LERP(st_lrm);
    LERP(st_s);
    LERP(masking_adj);
    LERP(masking_adj_short);
    LERP(ath_lower);
    LERP(ath_curve);
    LERP(ath_sensitivity);
    LERP(interch);
    NOOP(safejoint);
    LERP(sfb21mod);
    LERP(msfix);
    LERP(minval);
    LERP(ath_fixpoint);

    (void) lame_set_VBR_q(gfp, set->vbr_q);
    SET_OPTION(quant_comp, set->quant_comp, -1);
    SET_OPTION(quant_comp_short, set->quant_comp_s, -1);
    if (set->expY) {
        (void) lame_set_experimentalY(gfp, set->expY);
    }
    SET_OPTION(short_threshold_lrm, set->st_lrm, -1);
    SET_OPTION(short_threshold_s, set->st_s, -1);
    SET_OPTION(maskingadjust, set->masking_adj, 0);
    SET_OPTION(maskingadjust_short, set->masking_adj_short, 0);
    if (lame_get_VBR(gfp) == vbr_mt || lame_get_VBR(gfp) == vbr_mtrh) {
        lame_set_ATHtype(gfp, 5);
    }
    SET_OPTION(ATHlower, set->ath_lower, 0);
    SET_OPTION(ATHcurve, set->ath_curve, -1);
    SET_OPTION(athaa_sensitivity, set->ath_sensitivity, 0);
    if (set->interch > 0) {
        SET_OPTION(interChRatio, set->interch, -1);
    }

    /* parameters for which there is no proper set/get interface */
    if (set->safejoint > 0) {
        (void) lame_set_exp_nspsytune(gfp, lame_get_exp_nspsytune(gfp) | 2);
    }
    if (set->sfb21mod > 0) {
        int const nsp = lame_get_exp_nspsytune(gfp);
        int const val = (nsp >> 20) & 63;
        if (val == 0) {
            int const sf21mod = (set->sfb21mod << 20) | nsp;
            (void) lame_set_exp_nspsytune(gfp, sf21mod);
        }
    }
    SET__OPTION(msfix, set->msfix, -1);

    if (enforce == 0) {
        gfp->VBR_q = a;
        gfp->VBR_q_frac = x;
    }
    gfp->internal_flags->cfg.minval = set->minval;
    {   /* take care of gain adjustments */
        double const x = fabs(gfp->scale);
        double const y = (x > 0.f) ? (10.f * log10(x)) : 0.f;
        gfp->internal_flags->cfg.ATHfixpoint = set->ath_fixpoint - y;
    }
}

static int
apply_abr_preset(lame_global_flags *gfp, int preset, int enforce) {
    typedef struct {
        int abr_kbps;
        int quant_comp;
        int quant_comp_s;
        int safejoint;
        FLOAT nsmsfix;
        FLOAT st_lrm;      /*short threshold */
        FLOAT st_s;
        FLOAT scale;
        FLOAT masking_adj;
        FLOAT ath_lower;
        FLOAT ath_curve;
        FLOAT interch;
        int sfscale;
    } abr_presets_t;


    /* *INDENT-OFF* */

    /* 
     *  Switch mappings for ABR mode
     */
    const abr_presets_t abr_switch_map[] = {
            /* kbps  quant q_s safejoint nsmsfix st_lrm  st_s  scale   msk ath_lwr ath_curve  interch , sfscale */
            {8,   9, 9, 0, 0,    6.60, 145, 0.95, 0,   -30.0, 11,  0.0012, 1}, /*   8, impossible to use in stereo */
            {16,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   -25.0, 11,  0.0010, 1}, /*  16 */
            {24,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   -20.0, 11,  0.0010, 1}, /*  24 */
            {32,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   -15.0, 11,  0.0010, 1}, /*  32 */
            {40,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   -10.0, 11,  0.0009, 1}, /*  40 */
            {48,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   -10.0, 11,  0.0009, 1}, /*  48 */
            {56,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   -6.0,  11,  0.0008, 1}, /*  56 */
            {64,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   -2.0,  11,  0.0008, 1}, /*  64 */
            {80,  9, 9, 0, 0,    6.60, 145, 0.95, 0,   .0,    8,   0.0007, 1}, /*  80 */
            {96,  9, 9, 0, 2.50, 6.60, 145, 0.95, 0,   1.0,   5.5, 0.0006, 1}, /*  96 */
            {112, 9, 9, 0, 2.25, 6.60, 145, 0.95, 0,   2.0,   4.5, 0.0005, 1}, /* 112 */
            {128, 9, 9, 0, 1.95, 6.40, 140, 0.95, 0,   3.0,   4,   0.0002, 1}, /* 128 */
            {160, 9, 9, 1, 1.79, 6.00, 135, 0.95, -2,  5.0,   3.5, 0,      1}, /* 160 */
            {192, 9, 9, 1, 1.49, 5.60, 125, 0.97, -4,  7.0,   3,   0,      0}, /* 192 */
            {224, 9, 9, 1, 1.25, 5.20, 125, 0.98, -6,  9.0,   2,   0,      0}, /* 224 */
            {256, 9, 9, 1, 0.97, 5.20, 125, 1.00, -8,  10.0,  1,   0,      0}, /* 256 */
            {320, 9, 9, 1, 0.90, 5.20, 125, 1.00, -10, 12.0,  0,   0,      0}  /* 320 */
    };

    /* *INDENT-ON* */

    /* Variables for the ABR stuff */
    int r;
    int actual_bitrate = preset;

    r = nearestBitrateFullIndex(preset);

    (void) lame_set_VBR(gfp, vbr_abr);
    (void) lame_set_VBR_mean_bitrate_kbps(gfp, (actual_bitrate));
    (void) lame_set_VBR_mean_bitrate_kbps(gfp, min_int(lame_get_VBR_mean_bitrate_kbps(gfp), 320));
    (void) lame_set_VBR_mean_bitrate_kbps(gfp, max_int(lame_get_VBR_mean_bitrate_kbps(gfp), 8));
    (void) lame_set_brate(gfp, lame_get_VBR_mean_bitrate_kbps(gfp));


    /* parameters for which there is no proper set/get interface */
    if (abr_switch_map[r].safejoint > 0)
        (void) lame_set_exp_nspsytune(gfp, lame_get_exp_nspsytune(gfp) | 2); /* safejoint */

    if (abr_switch_map[r].sfscale > 0)
        (void) lame_set_sfscale(gfp, 1);


    SET_OPTION(quant_comp, abr_switch_map[r].quant_comp, -1);
    SET_OPTION(quant_comp_short, abr_switch_map[r].quant_comp_s, -1);

    SET__OPTION(msfix, abr_switch_map[r].nsmsfix, -1);

    SET_OPTION(short_threshold_lrm, abr_switch_map[r].st_lrm, -1);
    SET_OPTION(short_threshold_s, abr_switch_map[r].st_s, -1);

    /* ABR seems to have big problems with clipping, especially at low bitrates */
    /* so we compensate for that here by using a scale value depending on bitrate */
    lame_set_scale(gfp, lame_get_scale(gfp) * abr_switch_map[r].scale);

    SET_OPTION(maskingadjust, abr_switch_map[r].masking_adj, 0);
    if (abr_switch_map[r].masking_adj > 0) {
        SET_OPTION(maskingadjust_short, abr_switch_map[r].masking_adj * .9, 0);
    } else {
        SET_OPTION(maskingadjust_short, abr_switch_map[r].masking_adj * 1.1, 0);
    }


    SET_OPTION(ATHlower, abr_switch_map[r].ath_lower, 0);
    SET_OPTION(ATHcurve, abr_switch_map[r].ath_curve, -1);

    SET_OPTION(interChRatio, abr_switch_map[r].interch, -1);

    (void) abr_switch_map[r].abr_kbps;

    gfp->internal_flags->cfg.minval = 5. * (abr_switch_map[r].abr_kbps / 320.);

    return preset;
}


int
apply_preset(lame_global_flags *gfp, int preset, int enforce) {
    /*translate legacy presets */
    switch (preset) {
        case R3MIX: {
            preset = V3;
            (void) lame_set_VBR(gfp, vbr_mtrh);
            break;
        }
        case MEDIUM:
        case MEDIUM_FAST: {
            preset = V4;
            (void) lame_set_VBR(gfp, vbr_mtrh);
            break;
        }
        case STANDARD:
        case STANDARD_FAST: {
            preset = V2;
            (void) lame_set_VBR(gfp, vbr_mtrh);
            break;
        }
        case EXTREME:
        case EXTREME_FAST: {
            preset = V0;
            (void) lame_set_VBR(gfp, vbr_mtrh);
            break;
        }
        case INSANE: {
            preset = 320;
            gfp->preset = preset;
            (void) apply_abr_preset(gfp, preset, enforce);
            lame_set_VBR(gfp, vbr_off);
            return preset;
        }
    }

    gfp->preset = preset;
    {
        switch (preset) {
            case V9:
                apply_vbr_preset(gfp, 9, enforce);
                return preset;
            case V8:
                apply_vbr_preset(gfp, 8, enforce);
                return preset;
            case V7:
                apply_vbr_preset(gfp, 7, enforce);
                return preset;
            case V6:
                apply_vbr_preset(gfp, 6, enforce);
                return preset;
            case V5:
                apply_vbr_preset(gfp, 5, enforce);
                return preset;
            case V4:
                apply_vbr_preset(gfp, 4, enforce);
                return preset;
            case V3:
                apply_vbr_preset(gfp, 3, enforce);
                return preset;
            case V2:
                apply_vbr_preset(gfp, 2, enforce);
                return preset;
            case V1:
                apply_vbr_preset(gfp, 1, enforce);
                return preset;
            case V0:
                apply_vbr_preset(gfp, 0, enforce);
                return preset;
            default:
                break;
        }
    }
    if (8 <= preset && preset <= 320) {
        return apply_abr_preset(gfp, preset, enforce);
    }

    gfp->preset = 0;    /*no corresponding preset found */
    return preset;
}
